'****************************************************************
'*  Name    : 12F675 Voltage Monitor.BAS                        *
'*  Author  : Frank Miller "Pulsar"                             *
'*  Email   : mail@pulsar.gs                                    *
'*  Date    : 7/30/2004                                         *
'*  Version : 1.0                                               *
'****************************************************************

' VOLTAGE MONITOR is an unusual circuit in that the monitoring is done backwards!
' There are situations when you want to monitor your own battery voltage and
' shut down the circuit at a critical voltage. The program gives you a 3 digit
' output from "430" (+4.30 volts) to "600" (+6.00 volts) which makes it easy to
' act on just about any voltage. The beauty of the program is in its ability to
' accurately calculate an offset for a voltage reference since they are all
' different when you look at the exact reference voltage, (eg. a 3.3v regulator
' can be as low as 3.247 volts which would throw off your reading significantly). 
' The only critical part of this circuit is during the actual programming. You
' must ensure you are powering the circuit with exactly 5.00 volts! After that,
' it is fully calibrated to read any voltage at any time. Accuracy AND display
' resolution down to 1/100 volt!

' The conventional method for doing this trick would require using the 1 and only
' Vref pin (GPIO.1) on the 12F675 and another GPIO pin for the ADC reading.
' That's too many pins, plus, you can't IN CIRCUIT PROGRAM if you have a voltage
' constantly going to GPIO.1's "CLOCK" pin. You would have to disconnect this
' voltage source every time you dump a new program with this routine attached.
' Obviously, this is not a good way to go.

' The other method would be to power the chip with an LDO (Low Drop Out) regulator.
' A great Vreg is the LM1117 at 3.3v that will stay regulated down to 4.37 volts
' of course "brown-out" is to be disabled and you need to use two 1% resistors to
' form an accurate voltage divider for the input. Remember that the voltage 
' reference chip (LM1117) would be powering the chip so that means that the 1024
' steps of the 10-bit ADC can not go above 3.3 volts. Because the +5v battery is
' over this ADC ceiling of 3.3 volts, you have to give the ADC input something 
' less via the divider network. This is still not a great way to monitor voltage. 

' Here's a better way to monitor your own voltage using only an inexpensive LDO
' 3.3 volt regulator (LM-1117), only one pin (any of the 6 available), and a few
' lines of code. Of course you must have a PIC with EEPROM. This routine was
' written specifically for the 12F675 since there are so few pins to work with. 

' First set the ADCON0 to use Vdd for the 'reference'. And yes, it'll be changing
' all the time which in turn, changes the 10-bit scale. Hang-on... it gets real
' simple, real fast. This is where things go backwards. The uP reads the +3.3v 
' all the time and never changes, however the ADC result will change because the
' ratio of the ADC ladder is changing due to Vdd changing! This is what's backwards.

' There are three "knowns" or "givens" so we can easily solve for the fourth.
' The three "givens" are: "REFERENCE" at 3.3 volts, "RESOLUTION" at 1024 and the
' "ADC" reading. All we need to do is solve for the 4th or "X". The trick is to
' use a little high school math to simply "Solve for X". This is not complicated1

'       ADC Resolution          "X"
'       -------------- : ------------------
'         ADC Result      Voltage Reference

'   Do the typical "cross multiply" to get a one line formula of:
'       (Resolution) x (Voltage Reference) = (ADC Result_x)

'   Now to plug in some numbers and divide by the "X":
'   (This example of all four sections filled in equates to a +5.00 volt battery)

'          1024     "X"
'         ------ : ------
'           665     3.3    (665 is an ADC result of an exact 5 volt power supply)

' Now we have 665_x = 3325. Now solve for X by dividing 3325 by 665 = +5.00v
' Since the RESOLUTION and VOLTAGE REFERENCE don't change we have a constant of
' 3325 and only need one line of code after the ADC finishes, but we're also
' dealing with a decimal point which we can't work with. The section below 
' labeled "Calculate" does repetitive division using the "//" (modulus) function.
' It doesn't give you the digits to the right of the decimal, rather, you get 
' what's left over from the first division as a whole number. You then do it 
' again and again to get three whole numbers. I've multiplied the first digit by
' 100 to make room for two "place settings" for simple addition at the end of a
' "tens" digit (the second division) and lastly, the third digit being the "units"
' We jus add 'em right up. If the number is +4.27 volts, the three digits would
' be "400" + "20"  "7". Your program just looks for the three digits to manipulate
' when to act on any voltage. Simple!

' This program will create an "offset" to compensate for one of the "knowns" that
' really IS NOT a great known... the voltage regulator! It can vary significantly
' since the spec is quite loose for our purposes, however, it will not vary once
' you have it in circuit. The problem is you can't rely on the rated 3.3 volt 
' value. This is why we will run the calibration step in the middle of the program
' on the VERY FIRST run through. It will create an offset and store it in memory
' for any subsequent cold start. Because the actual programming is critical on 
' this calibration step, you MUST verify you have a very solid +5.00 volts on the
' chip. In effect, the program assumess it's getting 5 volts during the routine.

' That's about it... the last few notes are for connections to the chip, etc. If
' you have any feedback or questions, please email to Frank Miller at
' mail@pulsar.gs

' NOTE: Before dumping the program to the 12F675, make sure you have EXACTLY +5v
' on the board since the calibration step is run first to correct the variance
' on the 3.3 volt regulator. This "offset" value is subsequently put into EEPROM
' for read-back on any future power-up, maintaining accurate voltage readings.
' If you notice your voltage readout is off a 'tad' from your volt meter reading
' it means your programming voltage was off by that same amount.

' LCD is SEETRON (Scott Edwards 4-Line LCD Serial LCD backpack) and powered
' from the Epic programmer. (It's ok... not much of a load!) You can't easily
' use the incoming 5v supply to power the LCD because when the supply
' volts drop below 4.6v, the LCD gets goofy.

' CONNECTIONS TO IC PINS:
'   Pin 2: LCD serial backpack
'   Pin 3: LM-1117 Low Drop Out voltage regulator

' For demonstration purposes, you'll notice that the progam is using 462 bytes
' of space, almost half of the 1000 total, however, in use there is no need for
' knowing this info, so the program space drops dramatically in half.

'-------------------------------------------------------------------------------
VARIABLES:   
    Sample      var word        ' ADC voltage reading (Pin 3 set by ADCON0)
    Group       var word        ' Collection of 20 ADC readings to average
    Base        var word        ' Sample voltage reading x 5 volts
    Volts       var WORD        ' Manipulated as a 2 digit reading (46 = +4.6v)
    Volts_1     var word        ' 100's (1st voltage digit)
    Volts_2     var word        ' 10's (2nd voltage digit)
    Volts_3     var word        ' 1's (3rd voltage digit)
    Remain      var word        ' Remainder used 
    Loop        var byte        ' Used in ADC routine to capture 20 readings
    LCD         var GPIO.5      ' 4-Line "Scott Edwards LCD" backpack on pin 2

    TRISIO = %010000            ' LCD (pin 1 = gpio.5) and Vref (pin 3 = gpio.4)
    eeprom 0, [0, 0]            ' Program forces first 2 memory locations to "0"
 
LCD_SETUP:                      ' Print labels once for a flicker-free display    
    SEROUT LCD, 4, [12, 14]     ' 4 = baud, 12 = clear screen, 14 = backlight on 
    serout LCD, 4, [16, 144, "  BASE = "]  ' 16 = put cursor at "144" location
    serout LCD, 4, [16, 164, "SAMPLE = "]  ' 20 characters per line
    serout LCD, 4, [16, 184, " VOLTS = "]  ' 4th line would start at "204" 
    
    '---------------------------------------------------------------------------
    ANSEL =  %00110010      ' Analog Select Register (page 42)
                            ' MSB 0 = n/a                             "0"
                            '     0 = FRC select (bit 3)              "011"
                            '     1 = FRC select (bit 2)
                            '     1 = FRC select (bit 1)
                            '     0 = analog input select (bit 4 of 4) "0010"
                            '     0 = analog input select (bit 3 of 4)
                            '     1 = analog input select (bit 2 of 4)
                            '     0 = analog input select (bit 1 of 4)
    '---------------------------------------------------------------------------
    ADCON0 = %10001101      ' ADC input on GPIO.4/AN3/pin3 (page 41)
                            ' MSB 1 = Right Justify                   "1"    
                            '     0 = Vdd as voltage reference        "0"
                            '     0 = n/a                             "00"
                            '     0 = n/a
                            '     1 = select channel 03 (bit 2 of 2)  "11"
                            '     1 = select channel 03 (bit 1 of 2)
                            '     0 = conversion status (see routine) "0"
                            ' LSB 1 = A/D converter module is on      "1"

'-------------------------------------------------------------------------------
ADC: 
    Let Sample = 0                  ' Clear any prior values
    Let Group = 0
    for loop = 1 to 20              ' Take 20 readings for better accuracy
    
    ADCON0.1 = 1                    ' Setup for manual ADC technique
    NOT_DONE:
        if ADCON0.1 = 1 then not_done ' Waiting for completion of cycle
        pause 10                    ' Give the reading a little settling time
        sample.highbyte = ADRESH    
        Sample.lowbyte = ADRESL     

    pause 10        
    let Group = Group + Sample      ' Collecting 20 samples
    next loop                       ' Continue back up for another reading
    let sample = Group/20           ' Accurate averaged reading to work with

'-------------------------------------------------------------------------------
CHECK_CALIBRATION:                  ' Check for a stored value for BASE

    Read 0, base.byte0              ' Read EEPROM memory locations 0 & 1 to form
    read 1, base.byte1              ' a 16-bit word from 2 bytes for "BASE" value
                                    
    if base = 0 then                ' If nothing there, do the calibration
        Base = sample * 5           ' Sample x 5! Set power supply exactly to 5v!
        Write 0, BASE.byte0         ' Put lower BASE value into EEPROM address 0
        write 1, base.byte1         ' Put higher BASE value into EEPROM address 1
    endif                           ' End of capturing the BASE value

'-------------------------------------------------------------------------------
CALCULATE:

    Let Volts_1 = Base / sample       ' This is the FIRST of three digits
    Let Volts_1 = Volts_1 * 100       ' Make a place holder for 2nd & 3rd digits
    Let remain = base // sample * 10  ' Get the modulus (//) and multiply x 10
    '------------------------------- 
    Let Volts_2 = Remain / sample     ' This is the SECOND of three digits
    Let Volts_2 = Volts_2 * 10        ' Make a place holder for 2nd & 3rd digits
    let remain = remain // sample * 10' Get the modulus (//) and multiply x 10
    '------------------------------- 
    Let Volts_3 = remain / Sample     ' This is the THIRD of three digits
    
    Let Volts=Volts_1+Volts_2+volts_3 ' 3-digit number with 1/100 volt accuracy
    
DISPLAY: 
    serout LCD, 4, [16, 153, #base]
    serout LCD, 4, [16, 173, #Sample]
    serout LCD, 4, [16, 193, #volts]

goto adc     
